﻿using System;
using System.Runtime.InteropServices;
using System.Windows;

namespace QuickUIT.Input
{
    /// <summary>
    /// Provides methods for manipulating mouse input. 
    /// </summary>
    public static class Mouse
    {
        public struct CursorPoint
        {
            public int X;
            public int Y;
        }

        [DllImport("user32.dll")]
        private static extern int SetCursorPos(int x, int y);

        [DllImport("user32.dll")]
        private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo);

        [DllImport("user32.dll")]
        internal static extern bool GetPhysicalCursorPos(ref CursorPoint lpPoint);

        /// <summary>
        /// Specifies mouse events that can be raised.
        /// </summary>
        [Flags]
        private enum MouseEvents
        {
            /// <summary>
            /// Left mouse button down.
            /// </summary>
            LeftDown = 0x00000002,

            /// <summary>
            /// Left mouse button up.
            /// </summary>
            LeftUp = 0x00000004,

            /// <summary>
            /// Middle mouse button down.
            /// </summary>
            MiddleDown = 0x00000020,

            /// <summary>
            /// Middle mouse button up.
            /// </summary>
            MiddleUp = 0x00000040,
            
            /// <summary>
            /// Right mouse button down.
            /// </summary>
            RightDown = 0x00000008,

            /// <summary>
            /// Right mouse button up.
            /// </summary>
            RightUp = 0x00000010,

            /// <summary>
            /// The mouse has moved.
            /// </summary>
            Move = 0x00000001,
        }

        /// <summary>
        /// Represents mouse buttons.
        /// </summary>
        public enum MouseButton
        {
            /// <summary>
            /// Left button
            /// </summary>
            Left = 1,

            /// <summary>
            /// Right button
            /// </summary>
            Right = 2,

            /// <summary>
            /// Middle button
            /// </summary>
            Middle = 3
        }

        /// <summary>
        /// Specifies the different states that a mouse button can have.
        /// </summary>
        private enum MouseButtonState
        {
            /// <summary>
            /// Mouse button is up.
            /// </summary>
            Up = 1,

            /// <summary>
            /// Mouse button is down.
            /// </summary>
            Down = 2
        }

        /// <summary>
        /// Moves the mouse cursor to the specified coordinates. 
        /// </summary>
        /// <param name="x">The x coordinate to move to.</param>
        /// <param name="y">The y coordinate to move to.</param>
        public static void Move(int x, int y)
        {
            SetCursorPos(x, y);
        }

        /// <summary>
        /// Moves the mouse cursor to the specified coordinates. 
        /// </summary>
        /// <param name="x">The x coordinate to move to.</param>
        /// <param name="y">The y coordinate to move to.</param>
        public static void Move(double x, double y)
        {
            Move((int)x, (int)y);
        }

        /// <summary>
        /// Performs a mouse click at the current mouse cursor position.
        /// </summary>
        /// <param name="button">Which button to use to perform the click.</param>
        public static void Click(MouseButton button)
        {
            switch (button)
            {
                case MouseButton.Left:
                    mouse_event((uint)MouseEvents.LeftDown, 0, 0, 0, 0);
                    mouse_event((uint)MouseEvents.LeftUp, 0, 0, 0, 0);
                    break;
                case MouseButton.Right:
                    mouse_event((uint)MouseEvents.RightDown, 0, 0, 0, 0);
                    mouse_event((uint)MouseEvents.RightUp, 0, 0, 0, 0);
                    break;
                case MouseButton.Middle:
                    mouse_event((uint)MouseEvents.MiddleDown, 0, 0, 0, 0);
                    mouse_event((uint)MouseEvents.MiddleUp, 0, 0, 0, 0);
                    break;
            }
        }

        /// <summary>
        /// Performs a mouse click at the specified mouse cursor position. 
        /// </summary>
        /// <param name="button">Which button to use to perform the click</param>
        /// <param name="x">The x coordinate to move to.</param>
        /// <param name="y">The y coordinate to move to.</param>
        public static void Click(MouseButton button, int x, int y)
        {
            Move(x, y);
            Click(button);
        }

        /// <summary>
        /// Performs a mouse click at the specified mouse cursor position. 
        /// </summary>
        /// <param name="button">Which button to use to perform the click</param>
        /// <param name="x">The x coordinate to move to.</param>
        /// <param name="y">The y coordinate to move to.</param>
        public static void Click(MouseButton button, double x, double y)
        {
            Click(button, (int) x, (int) y);
        }

        /// <summary>
        /// Performs a double click with the left mouse button at the current mouse cursor position. 
        /// </summary>
        public static void DoubleClick()
        {
            Click(MouseButton.Left);
            Click(MouseButton.Left);
        }

        /// <summary>
        /// Performs a double click with the left mouse button at the specified mouse cursor position. 
        /// </summary>
        /// <param name="x">The x coordinate.</param>
        /// <param name="y">The y coordinate.</param>
        public static void DoubleClick(int x, int y)
        {
            Move(x, y);
            DoubleClick();
        }

        /// <summary>
        /// Performs a double click with the left mouse button at the specified mouse cursor position. 
        /// </summary>
        /// <param name="x">The x coordinate.</param>
        /// <param name="y">The y coordinate.</param>
        public static void DoubleClick(double x, double y)
        {
            DoubleClick((int)x, (int)y);
        }

        /// <summary>
        /// Gets the current mouse cursor position. 
        /// </summary>
        /// <returns>A point representing the current cursor position.</returns>
        public static Point GetPosition()
        {
            var cursorPos = new CursorPoint();
            GetPhysicalCursorPos(ref cursorPos);
            return new Point(cursorPos.X, cursorPos.Y);
        }

        /// <summary>
        /// Presses a mouse button down at the current mouse cursor position. 
        /// </summary>
        /// <param name="button">The button to hold down.</param>
        public static void ButtonDown(MouseButton button)
        {
            SetMouseButtonState(button, MouseButtonState.Down);
        }

        /// <summary>
        /// Releases a mouse button from the down position at the current mouse cursor position. 
        /// </summary>
        /// <param name="button"></param>
        public static void ButtonUp(MouseButton button)
        {
            SetMouseButtonState(button, MouseButtonState.Up);
        }

        /// <summary>
        /// Performs a mouse click at the current mouse cursor positon.  
        /// </summary>
        /// <param name="button">The mouse button to click.</param>
        /// <param name="state">The <see cref="MouseButtonState"/> of the mouse button.</param>
        private static void SetMouseButtonState(MouseButton button, MouseButtonState state)
        {
            switch (button)
            {
                case MouseButton.Left:
                    switch (state)
                    {
                        case MouseButtonState.Up:
                            mouse_event((uint)MouseEvents.LeftUp, 0, 0, 0, 0);
                            break;
                        case MouseButtonState.Down:
                            mouse_event((uint)MouseEvents.LeftDown, 0, 0, 0, 0);
                            break;
                    }
                    break;
                case MouseButton.Right:
                    switch (state)
                    {
                        case MouseButtonState.Up:
                            mouse_event((uint)MouseEvents.RightUp, 0, 0, 0, 0);
                            break;
                        case MouseButtonState.Down:
                            mouse_event((uint)MouseEvents.RightDown, 0, 0, 0, 0);
                            break;
                    }
                    break;
                case MouseButton.Middle:
                    switch (state)
                    {
                        case MouseButtonState.Up:
                            mouse_event((uint)MouseEvents.MiddleUp, 0, 0, 0, 0);
                            break;
                        case MouseButtonState.Down:
                            mouse_event((uint)MouseEvents.MiddleDown, 0, 0, 0, 0);
                            break;
                    }
                    break;
            }
        }
    }
}
